package org.jradweb.core.utils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.jradweb.core.orm.AbstractModel;
import org.jradweb.core.orm.HibernateModel;
import org.jradweb.core.orm.Model;
import org.jradweb.core.orm.MultiSiteAbstractModel;

public class BeanUtils {
	private static Logger log = Logger.getLogger(BeanUtils.class);

	private String MESSAGE_KEY_NOT_INTEGER = "validator.type.notInteger";
	private String MESSAGE_KEY_NOT_NUMBER = "validator.type.notInteger";
	private String MESSAGE_KEY_NOT_DATE = "validator.type.notInteger";

	public static void setProperties(Model model, Map<String, String> params) {
		for (String key : params.keySet()) {
			Method m = getSetMethod(key, model.getClass());
			if (m != null && !m.getName().equals("setId")
					&& !m.getName().equals("setUpdatedAt")
					&& !m.getName().equals("setCreatedAt")) {

				Class[] pts = m.getParameterTypes();
				if (pts.length == 1) {
					try {
						if (pts[0].equals(String.class)) {
							Object value = params.get(key);
							String strValue = (String) params.get(key);
							m.invoke(model, strValue);
						} else if (pts[0].equals(Integer.class)) {
							m.invoke(model, Integer.parseInt(params.get(key)));
						} else if (pts[0].equals(Date.class)) {
							Date date = DateFormat.parseDate(params.get(key));
							m.invoke(model, date);
						} else if (pts[0].equals(Float.class)) {
							Float floatValue = Float
									.parseFloat(params.get(key));
							m.invoke(model, floatValue);
						}

					} catch (IllegalArgumentException e) {
						e.printStackTrace();
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					} catch (InvocationTargetException e) {
						e.printStackTrace();
					} catch (ParseException e) {
						e.printStackTrace();
					}
				}
			}
		}
		// need convert date time value if the server time is not GMT
		model.setUpdatedAt(new Date());
	}
	/**
	 * set exact class to target
	 * @param obj
	 * @param propertyName
	 * @param propertyValue
	 */
	public static void setPropertyValue(Object obj, String propertyName,
			Object propertyValue) {
		Method method = getSetMethod(propertyName, obj.getClass());
		try {
			method.invoke(obj, propertyValue);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}

	private static void castStringAndsetPropertyValue(Object model,
			String propertyName, String strValue) {
		Method m = getSetMethod(propertyName, model.getClass());
		if(m==null){
			throw new NullPointerException(model.getClass() +"  has no set property \""+propertyName+"\"");
		}
		Class[] pts = m.getParameterTypes();
		strValue=StringUtils.trimToNull(strValue);
		if (pts.length == 1) {
			try {
				if (pts[0].equals(String.class)) {
					m.invoke(model, strValue);
				} else if (pts[0].equals(Integer.class)||pts[0].equals(int.class)) {
					Integer intValue=null;
					if(strValue!=null){
						intValue=Integer.parseInt(strValue);
						
					}
					m.invoke(model, intValue);
				} else if (pts[0].equals(Boolean.class)||pts[0].equals(boolean.class)) {
					Boolean value = null;
					if(strValue!=null){
						Boolean.parseBoolean(strValue);
					}
					m.invoke(model, value);
				} else if (pts[0].equals(Date.class)) {
					Date date =null;
					if(strValue!=null){
						date = DateFormat.parseDate(strValue);
					}
					m.invoke(model, date);
				} else if (pts[0].equals(Float.class)||pts[0].equals(float.class)) {
					Float floatValue = null;
					if(strValue!=null){
						floatValue=Float.parseFloat(strValue);
					}
					m.invoke(model, floatValue);
				} else if (pts[0].equals(Double.class)||pts[0].equals(double.class)) {
					Double doubleValue = null;
					if(strValue!=null){
						doubleValue = Double.parseDouble(strValue);
					}
					m.invoke(model, doubleValue);
				} else if (pts[0].getSuperclass() != null
						&& pts[0].getSuperclass().equals(AbstractModel.class)) {
					Integer id = Integer.parseInt(strValue);
					AbstractModel setModel = (AbstractModel) AbstractModel.get(
							pts[0], id);
					m.invoke(model, setModel);
				}

			} catch (IllegalArgumentException e) {
				throw new RuntimeException(e);
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			} catch (InvocationTargetException e) {
				throw new RuntimeException(e);
			} catch (ParseException e) {
				throw new RuntimeException(e);
			} catch (SecurityException e) {
				throw new RuntimeException(e);
			}
		}
	}

	public static Object getPropertyValue(Object obj, String propertyName) {
		try {
			Method method = getGetMethod(obj.getClass(), propertyName);

			return method.invoke(obj);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			throw new RuntimeException(e);
		}
	}

	/*
	 * set property like "xx.xxx"
	 */
	private static void setPropertyValues(Object obj, String propertyName,
			String propertyValue) {
		//TODO 在子对象都只用ID的时候可以这样处理
//		if(propertyName.indexOf(".")>0&&(propertyValue==null||propertyValue.length()==0)){
//			return;
//		}
		
		String[] props = org.apache.commons.lang.StringUtils.split(
				propertyName, '.');
		String pv=org.apache.commons.lang.StringUtils.trimToNull(propertyValue);
		Object currentObj = obj;
		for (int i = 0; i < props.length; i++) {
			String prop = props[i];
			if (i < (props.length - 1)) {
				Object subObj = getPropertyValue(currentObj, prop);
				if (subObj == null) {
					//如果子对象为空，并且要设的值为空，则直接返回，不创建子对象
					if(pv==null||pv.length()==0){
						return;
					}
					
					//继续
					Method method = getGetMethod(currentObj.getClass(),
							prop);
					Class returnType = method.getReturnType();
					try {
						subObj = returnType.newInstance();
						setPropertyValue(currentObj, prop, subObj);
						currentObj=subObj;
					} catch (InstantiationException e) {
						throw new RuntimeException(e);
					} catch (IllegalAccessException e) {
						throw new RuntimeException(e);
					}
				}else{
					if(props[i+1].equals("id")&&pv==null){
						setPropertyValue(currentObj, prop, null);
						return;
					}
					currentObj = subObj;
				}
			} else {
				castStringAndsetPropertyValue(currentObj, prop, pv);
			}
		}
	}

	private static Map<String, String> getPropertyKeys(AbstractModel model,
			HttpServletRequest request) {
		Set<String> allKey = request.getParameterMap().keySet();
		Map<String, String> keys = new HashMap<String, String>();
		for (String key : allKey) {
			List<Method> ms = getSetMethods(key, model.getClass());
			if (ms.size() > 0) {

				keys.put(key, request.getParameter(key));
			}
		}
		return keys;
	}

	public static void setAbstractModelProperties(AbstractModel model,
			HttpServletRequest request) {
		Map<String, String> params = getPropertyKeys(model, request);
		for (String key : params.keySet()) {
			setPropertyValues(model, key, params.get(key));
		}
	}

	public static void setMultiSiteAbstractModelProperties(
			MultiSiteAbstractModel model, HttpServletRequest request) {
		if (model.getId() == null || model.getId().getId() == null) {
			try {

			} catch (NumberFormatException e) {
				String msg = "The value of parameter siteId \""
						+ request.getParameter("id") + "\" is not a number!";
				log.error(msg);
				throw new NumberFormatException(msg);
			} catch (NullPointerException e) {
				String msg = "The value of parameter siteId could not be null!";
				log.error(msg);
				throw new NullPointerException(msg);
			}
		}

		for (String key : (Set<String>) request.getParameterMap().keySet()) {
			Method m = getSetMethod(key, model.getClass());
			if (m != null && !m.getName().equals("setId")
					&& !m.getName().equals("setUpdatedAt")
					&& !m.getName().equals("setCreatedAt")
					&& m.getName().startsWith("set")) {

				Class[] pts = m.getParameterTypes();
				if (pts.length == 1) {
					try {
						if (pts[0].equals(String.class)) {

							String strValue = (String) request
									.getParameter(key);

							m.invoke(model, strValue);
						} else if (pts[0].equals(Integer.class)) {
							try {
								m.invoke(model, Integer.parseInt(request
										.getParameter(key)));
							} catch (NumberFormatException e) {
								log.error(e.getMessage());
							}
						} else if (pts[0].equals(Date.class)) {
							Date date = DateFormat.parseDate(request
									.getParameter(key));
							m.invoke(model, date);
						} else if (pts[0].equals(Float.class)) {
							Float floatValue = Float.parseFloat(request
									.getParameter(key));
							m.invoke(model, floatValue);
						} else if (pts[0].getSuperclass().equals(
								AbstractModel.class)) {
							Integer id = Integer.parseInt(request
									.getParameter(key));
							AbstractModel setModel = (AbstractModel) AbstractModel
									.get(pts[0], id);
							m.invoke(model, setModel);
						}

					} catch (IllegalArgumentException e) {
						throw new RuntimeException(e);
					} catch (IllegalAccessException e) {
						throw new RuntimeException(e);
					} catch (InvocationTargetException e) {
						throw new RuntimeException(e);
					} catch (ParseException e) {
						throw new RuntimeException(e);
					} catch (SecurityException e) {
						throw new RuntimeException(e);
					}
				}
			}
		}
		// need convert date time value if the server time is not GMT
		model.setUpdatedAt(new Date());
	}

	public static void setProperties(HibernateModel model,
			HttpServletRequest request) {
		if (AbstractModel.class.isInstance(model)) {
			setAbstractModelProperties((AbstractModel) model, request);
		} else if (MultiSiteAbstractModel.class.isInstance(model)) {
			setMultiSiteAbstractModelProperties((MultiSiteAbstractModel) model,
					request);
		}
	}

	public static Method getGetMethod(Class objectClass, String propertyName) {
		try {
			String methodName = "get"
					+ StringUtils.upperCaseFirst(propertyName);
			Method method = objectClass.getMethod(methodName);

			return method;
		} catch (SecurityException e) {
			throw new RuntimeException(e);
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		} catch (IllegalArgumentException e) {
			throw new RuntimeException(e);
		}
	}

	private static Method getSetMethod(String propertyName, Class modelClass) {

		List<Method> method = new ArrayList<Method>();

		for (Method m : modelClass.getMethods()) {

			if (m.getName().equals(
					"set" + StringUtils.upperCaseFirst(propertyName))) {
				return m;
			}
		}

		return null;

	}

	public static List<Method> getSetMethods(String propertyName,
			Class modelClass) {

		
		String[] props = org.apache.commons.lang.StringUtils.split(
				propertyName, '.');
		return getSetMethods(props, modelClass);

	}
	/**
	 * 
	 * @param propertyNames splited property name array.
	 * @param modelClass
	 * @return
	 */
	public static List<Method> getSetMethods(String[] propertyNames,
			Class modelClass) {

		List<Method> methods = new ArrayList<Method>();
		
		Class currentClass = modelClass;
		for (int i = 0; i < propertyNames.length; i++) {
			String prop = propertyNames[i];

			Method setMethod = getSetMethod(prop, currentClass);
			if (setMethod == null || setMethod.getParameterTypes().length != 1) {
				//if there is no set method or method is illegal, the propertyName has no corressponding set method
				methods.clear();
				return methods;
			}

			Class subClass = setMethod.getParameterTypes()[0];
			currentClass = subClass;
			methods.add(setMethod);
		}

		return methods;

	}

}
